package org.vacoor.nothing.web.servlet;

import org.vacoor.nothing.common.misc.el.ELEngine;
import org.vacoor.nothing.common.util.ThreadContext;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Collections;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import java.util.Set;

/**
 * Web 请求上下文
 *
 * @author vacoor
 */
@SuppressWarnings({"unchecked", "unused"})
public class WebRequestContext {
    private static final String THREAD_CONTEXT_KEY = WebRequestContext.class.getName() + ".THREAD_CONTEXT_KEY";

    private static final int INVALID_SCOPE = -1;
    public static final int CONTEXT_SCOPE = 0;
    public static final int REQUEST_SCOPE = 1;
    public static final int SESSION_SCOPE = 2;
    public static final int APPLICATION_SCOPE = 3;

    private final HttpServletRequest request;
    private final HttpServletResponse response;
    private final ServletContext servletContext;
    private final ELEngine elEngine;

    public WebRequestContext(final HttpServletRequest request, final HttpServletResponse response, final ServletContext servletContext) {
        this.request = assertNotNull(request, "request");
        this.response = assertNotNull(response, "response");
        this.servletContext = assertNotNull(servletContext, "servlet context");
        this.elEngine = new ContextElEngine(WebRequestContext.class, this);
    }

    private <T> T assertNotNull(T o, String name) {
        if (null == o) {
            throw new NullPointerException(name + " must be not null");
        }
        return o;
    }

    /* **********************************
     *        Holder methods
     * **********************************/

    public HttpServletRequest getRequest() {
        return request;
    }

    public HttpServletResponse getResponse() {
        return response;
    }

    public ServletContext getServletContext() {
        return servletContext;
    }

    public HttpSession getSession() {
        return getSession(true);
    }

    public HttpSession getSession(boolean create) {
        return getRequest().getSession(create);
    }

    /* ****************************************
     *        find attribute method
     * ****************************************/


    public <T> T findAttribute(String name) {
        T value = getAttribute(name, CONTEXT_SCOPE);
        if (value != null) return value;
        value = getAttribute(name, REQUEST_SCOPE);
        if (value != null) return value;
        value = getAttribute(name, SESSION_SCOPE);
        if (value != null) return value;
        return getAttribute(name, APPLICATION_SCOPE);
    }


    @SuppressWarnings("unchecked")
    public <T> T getAttribute(String name, int scope) {
        if (CONTEXT_SCOPE == scope) {
            return get(name);
        }
        if (REQUEST_SCOPE == scope) {
            return (T) getRequest().getAttribute(name);
        }
        if (SESSION_SCOPE == scope) {
            HttpSession session = getSession(false);
            return null != session ? (T) session.getAttribute(name) : null;
        }
        if (APPLICATION_SCOPE == scope) {
            return (T) getServletContext().getAttribute(name);
        }
        throw new IllegalArgumentException("Invalid scope " + scope);
    }


    public void setAttribute(String name, Object value, int scope) {
        if (CONTEXT_SCOPE == scope) {
            bind(name, value);
        } else if (REQUEST_SCOPE == scope) {
            getRequest().setAttribute(name, value);
        } else if (SESSION_SCOPE == scope) {
            getSession().setAttribute(name, value);
        } else if (APPLICATION_SCOPE == scope) {
            getServletContext().setAttribute(name, value);
        } else {
            throw new IllegalArgumentException("Invalid scope " + scope);
        }
    }


    public void removeAttribute(String name, int scope) {
        if (CONTEXT_SCOPE == scope) {
            unbind(name);
        } else if (REQUEST_SCOPE == scope) {
            getRequest().removeAttribute(name);
        } else if (SESSION_SCOPE == scope) {
            getSession().removeAttribute(name);
        } else if (APPLICATION_SCOPE == scope) {
            getServletContext().removeAttribute(name);
        } else {
            throw new IllegalArgumentException("Invalid scope " + scope);
        }
    }


    public int getAttributesScope(String name) {
        if (getAttribute(name, CONTEXT_SCOPE) != null) return CONTEXT_SCOPE;
        if (getAttribute(name, REQUEST_SCOPE) != null) return REQUEST_SCOPE;
        if (getAttribute(name, SESSION_SCOPE) != null) return SESSION_SCOPE;
        if (getAttribute(name, APPLICATION_SCOPE) != null) return APPLICATION_SCOPE;
        return INVALID_SCOPE;
    }


    @SuppressWarnings("unchecked")
    public Enumeration<String> getAttributeNamesInScope(int scope) {
        if (CONTEXT_SCOPE == scope) {
            return Collections.enumeration(getNames());
        }
        if (REQUEST_SCOPE == scope) {
            return getRequest().getAttributeNames();
        }
        if (SESSION_SCOPE == scope) {
            HttpSession session = getSession(false);
            return null != session ? session.getAttributeNames() : EMPTY_ENUM;
        }
        if (APPLICATION_SCOPE == scope) {
            return getServletContext().getAttributeNames();
        }
        throw new IllegalArgumentException("Invalid scope " + scope);
    }

    /* *****************************************
     *       expression resolve method
     * *****************************************/

    /**
     * resolve("name: ${param.name}")
     *
     * @param expression EL 表达式
     * @return
     */
    public String resolve(String expression) {
        return elEngine.resolve(expression);
        // return elEngine.resolve("${" + expression + "}");
    }

    public <T> T resolve(String expression, Class<T> type) {
        return elEngine.resolve(expression, type);
        // return elEngine.resolve("${" + expression + "}", type);
    }

    /* ****************************************
     *
     * ****************************************/

    public WebRequestContext bind() {
        bind(THREAD_CONTEXT_KEY, this);
        return this;
    }

    public void unbind() {
        unbind(THREAD_CONTEXT_KEY);
    }

    /* **************************************************
     *
     * **************************************************/

    public static WebRequestContext getContext() {
        return get(THREAD_CONTEXT_KEY);
    }

    private static void bind(String key, Object value) {
        ThreadContext.put(key, value);
    }

    private static void unbind(String key) {
        ThreadContext.remove(key);
    }

    private static <T> T get(String key) {
        return (T) ThreadContext.get(key);
    }

    @SuppressWarnings("rawtypes")
    private static Set getNames() {
        return ThreadContext.getResources().keySet();
    }

    /* ************************************************
     *
     * ************************************************/
    private static final Enumeration<String> EMPTY_ENUM = new Enumeration<String>() {
        public boolean hasMoreElements() {
            return false;
        }

        public String nextElement() {
            throw new NoSuchElementException();
        }
    };

    private static class ContextElEngine extends ELEngine {
        ContextElEngine(Class<?> contextKey, Object context) {
            super();
            this.resolver.add(new ImplicitObjectELResolver());
            this.resolver.add(new ScopedAttributeELResolver());
            this.context.putContext(contextKey, context);
        }
    }
}
